//------------------------------------------------------------------------------ 
//  <copyright file="ImageFrame.h" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//  </copyright>
//------------------------------------------------------------------------------
#pragma once

#include <vector>

using namespace System;
using namespace System::Collections::Generic;
using namespace System::Runtime::CompilerServices;
using namespace System::Runtime::InteropServices;
using namespace System::Runtime::Serialization;
using namespace System::Text;
using namespace System::Threading::Tasks;

namespace Microsoft 
{
	namespace Robotics 
	{
		namespace Vision
		{
			/// <summary>
			/// A struct containing the 3 bytes of an RGB pixel.
			/// </summary>
			[StructLayout(LayoutKind::Sequential)]
			public value struct RgbPix
			{
			public:
				/// <summary>
				/// The red, green, and blue pixel values
				/// </summary>
				unsigned char Red, Green, Blue;

				/// <summary>
				/// Initializes a new instance of the <see cref="RgbPix"/> struct.
				/// </summary>
				/// <param name="r">Red value.</param>
				/// <param name="g">Green value.</param>
				/// <param name="b">Blue value.</param>
				RgbPix(unsigned char r, unsigned char g, unsigned char b)
				{
					this->Red = r;
					this->Green = g;
					this->Blue = b;
				}

				/// <summary>
				/// Operator +
				/// </summary>
				/// <param name="left">Left argument</param>
				/// <param name="right">Right argument</param>
				static RgbPix operator+(RgbPix left, RgbPix right) {return RgbPix((left.Red + right.Red), (left.Green + right.Green), (left.Blue + right.Blue));}

				/// <summary>
				/// Operator * with scalar
				/// </summary>
				/// <param name="left">Left argument</param>
				/// <param name="scalar">Scalar multiply</param>
				static RgbPix operator*(RgbPix left, double scalar) {return RgbPix((unsigned char)(left.Red * scalar), (unsigned char)(left.Green * scalar), (unsigned char)(left.Blue * scalar));}

				/// <summary>
				/// Operator * with scalar
				/// </summary>
				/// <param name="left">Left argument</param>
				/// <param name="scalar">Scalar multiply</param>
				static RgbPix operator/(RgbPix left, double scalar) {return RgbPix((unsigned char)(left.Red / scalar), (unsigned char)(left.Green / scalar), (unsigned char)(left.Blue / scalar));}
				/// <summary>
				/// Operator == 
				/// </summary>
				/// <param name="left">Left argument</param>
				/// <param name="scalar">Scalar multiply</param>
				static bool operator==(RgbPix left, RgbPix right) {return ((left.Red == right.Red) && (left.Green == right.Green) && (left.Blue == right.Blue));}
			};

			/// <summary>
			/// A struct containing the 2 unsigned chars of a YUY2 pixel.
			/// </summary>
			[StructLayout(LayoutKind::Sequential)]
			public value struct Yuy2Pix
			{
			public:
				/// <summary>
				/// The luminance value
				/// </summary>
				unsigned char Y;

				/// <summary>
				/// The chrominance value.  This value cannot be interpreted without knowing the pixel coordinates and the values of neighboring pixels
				/// </summary>
				unsigned char U;

				/// <summary>
				/// Initializes a new instance of the <see cref="Yuy2Pix"/> struct.
				/// </summary>
				/// <param name="lum">The luminance value.</param>
				/// <param name="chrom">The chrominance value.</param>
				Yuy2Pix(unsigned char lum, unsigned char chrom)
				{
					this->Y = lum;
					this->U = chrom;
				}
			};

			/// <summary>
			/// A struct describing the pixel surface normal
			/// </summary>
			[StructLayout(LayoutKind::Sequential)]
			public value struct PixNormal
			{
			public:
				/// <summary>
				/// Angle with x axis
				/// </summary>
				SByte CosX;

				/// <summary>
				/// Angle with y axis
				/// </summary>
				SByte CosY;

				/// <summary>
				/// Angle with z axis
				/// </summary>
				SByte CosZ;

				/// <summary>
				/// Initializes a new instance of the <see cref="SurfaceNormal"/> struct.
				/// </summary>
				/// <param name="cosX">Cosine of angle with x axis</param>
				/// <param name="cosY">Cosine of angle with y axis</param>
				/// <param name="cosZ">Cosine of angle with z axis</param>
				PixNormal(SByte cosX, SByte cosY, SByte cosZ) : CosX(cosX), CosY(cosY), CosZ(cosZ)
				{
				}

				/// <summary>
				/// Operator *
				/// </summary>
				/// <param name="left">Left argument</param>
				/// <param name="right">Right argument</param>
				static PixNormal operator*(double left, PixNormal right) {return PixNormal((SByte)(left * right.CosX), (SByte)(left * right.CosY), (SByte)(left * right.CosZ));}

				/// <summary>
				/// Operator +
				/// </summary>
				/// <param name="left">Left argument</param>
				/// <param name="right">Right argument</param>
				static PixNormal operator+(PixNormal left, PixNormal right) {return PixNormal((left.CosX + right.CosX), (left.CosY + right.CosY), (left.CosZ + right.CosZ));}
			};

			/// <summary>
			/// A class which represents an image frame.  The image can be a depth frame or color frame, or something else.
			/// In the future, we may want to add a caching mechanism, to enable prevent needlessly recomputing information that has already been computed.  
			/// For example, if someone extracts edgels from the image, then fits curves to the edgels, and then someone else wants to extract edgels, the 
			/// cache would allow the edgels computed for the curves to be reused, rather than computing them again from scratch.  It is anticipated that
			/// the cache will primarily consist of higher-level data derived from the image, rather than other processed images.  However, it is conceivable
			/// that it will be useful to cache such data as well, e.g., a smoothed version of the image, or the luminance of an RGB image.
			/// The cache, when it is implemented, will be a class.  The cache object will be owned by the ImageFrame class, which will provide public access
			/// to the cache via a property that contains only a get method (no set method).  The get method will create the cache if it does not exist; otherwise
			/// it will return the existing cache.
			/// </summary>
			/// <typeparam name="T">Pixel type.</typeparam>
			generic <typename T>
			where T : value class
			[DataContract]
			[StructLayout(LayoutKind::Sequential)]
			public ref class ImageFrame 
			{
			private:
				/// <summary>
				/// Image data stored as a 1D array of pixels.
				/// </summary>
				[DataMember]
				array<T>^ imageData;

				/// <summary>
				/// Image width in pixels.
				/// </summary>
				[DataMember]
				int width;

				/// <summary>
				/// Image height in pixels.
				/// </summary>
				[DataMember]
				int height;

				/// <summary>
				/// Image stride in unsigned chars.
				/// For now, stride = width * unsigned charsPerPixel, but this is not guaranteed to be the case
				/// </summary>
				[DataMember]
				int stride;

			protected:
				/// <summary>
				/// Initializes a new instance of the ImageFrame class.
				/// </summary>
				/// <param name="width">Image width in pixels.</param>
				/// <param name="height">Image height in pixels.</param>
				/// <param name="data">A buffer that already contains image data. The new instance will referrenc this buffer.</param>
				ImageFrame(int width, int height, array<T>^ data)
				{
					this->width = width;
					this->height = height;
					this->stride = width * this->BytesPerPixel;
					this->imageData = data;
				}

			public:

				/// <summary>
				/// Initializes a new instance of the ImageFrame class.
				/// </summary>
				/// <param name="width">Image width in pixels.</param>
				/// <param name="height">Image height in pixels.</param>
				ImageFrame(int width, int height)
				{
					this->width = width;
					this->height = height;
					this->stride = width * this->BytesPerPixel;
					this->imageData = gcnew array<T>(width * height);
				}

				/// <summary>
				/// Initializes a new instance of the ImageFrame class.
				/// Makes a shallow copy of the original frame (data buffer is shared).
				/// </summary>
				/// <param name="other">Original image frame</param>
				ImageFrame(ImageFrame<T>^ other)
				{
					this->width = other->Width;
					this->height = other->Height;
					this->stride = other->Stride;
					this->imageData = other->imageData;
				}

				/// <summary>
				/// Clones (makes a deep copy) of this image
				/// </summary>
				ImageFrame<T>^ Clone()
				{
					ImageFrame<T>^ clone = gcnew ImageFrame<T>(this->Width, this->Height);
					array<T>::Copy(this->ImageData, clone->ImageData, this->Width * this->Height);
					return clone;
				}

				/// <summary>
				/// Gets the image data as a 1D array.  
				/// This low-level access should be avoided except as needed for interfacing with external low-level functions.
				/// </summary>
				property array<T>^ ImageData
				{
					array<T>^ get()
					{
						return this->imageData;
					}
				}

				/// <summary>
				/// Gets the image width in pixels.
				/// </summary>
				property int Width
				{
					int get()
					{
						return this->width;
					}
				}

				/// <summary>
				/// Gets the image height in pixels.
				/// </summary>
				property int Height
				{
					int get()
					{
						return this->height;
					}
				}

				/// <summary>
				/// Gets the number of unsigned chars per pixel.  1 for 8-bit grayscale, 2 for 16-bit depth, 3 for RGB24.
				/// </summary>
				property int BytesPerPixel
				{
					int get()
					{
						return sizeof(T);
					}
				}

				/// <summary>
				/// Gets the image stride in unsigned chars.
				/// </summary>
				property int Stride
				{
					int get()
					{
						return this->stride;
					}
				}

				// <summary>
				// Gets or sets the pixel at (x,y).  
				// This is the preferred way of accessing pixels, and should be as fast as directly accessing the 1D array (according to our timing tests).
				// </summary>
				// <param name="x">horizontal coordinate of pixel, from top-left corner.</param>
				// <param name="y">vertical coordinate of pixel, from top-left corner.</param>
				// <returns>The value of the pixel.</returns>
				property T default[int, int]
				{
					T get(int x, int y)
					{
						return this->imageData[y * this->Width + x];
					}

					void set(int x, int y, T value)
					{
						this->imageData[y * this->Width + x] = value;
					}
				}

				// <summary>
				// Gets or sets the pixel at a given 1D index.  
				// This is a potentially faster way to access pixels compared with 2D indexing, when the 1D index is already known.
				// Note that it is easy to convert b/w 2D and 1D indices:  index = x * width + y
				// </summary>
				// <param name="index">1D index, in row-major order from top-left corner.</param>
				// <returns>The value of the pixel.</returns>
				property T default[int]
				{
					T get(int index)
					{
						return this->imageData[index];
					}

					void set(int index, T value)
					{
						this->imageData[index] = value;
					}
				}
			};

			/// <summary>
			/// Image class for depth image.
			/// </summary>
			[DataContract]
			public ref class ImageFrameDepth : ImageFrame<signed short>
			{
			public:
				/// <summary>
				/// Initializes a new instance of the ImageFrameDepth class.
				/// </summary>
				/// <param name="width">Image width in pixels.</param>
				/// <param name="height">Image height in pixels.</param>
				ImageFrameDepth(int width, int height) : ImageFrame(width, height) 
				{
				}

				/// <summary>
				/// Initializes a new instance of the ImageFrame class.
				/// </summary>
				/// <param name="width">Image width in pixels.</param>
				/// <param name="height">Image height in pixels.</param>
				/// <param name="data">A buffer that already contains image data. The new instance will reference this buffer.</param>
				ImageFrameDepth(int width, int height, array<signed short>^ data) : ImageFrame(width, height, data) 
				{
				}

				
				/// <summary>
				/// Initializes a new instance of the <see cref="ImageFrameDepth"/> class, from another instance
				/// Performs shallow initialization
				/// </summary>
				ImageFrameDepth(ImageFrame<short>^ other) : ImageFrame<short>(other)
				{
				}

				/// <summary>
				/// Return the frame data as an unsigned short array
				/// </summary>
				array<unsigned short>^ GetDataAsUnsigned()
				{
					array<unsigned short>^ buf = (array<unsigned short>^)this->ImageData;
					return buf;
				}
			};

			/// <summary>
			/// Image class for grayscale (one unsigned char per pixel) image.
			/// </summary>
			[DataContract]
			public ref class ImageFrameGray8 : ImageFrame<unsigned char>
			{
			public:
				/// <summary>
				/// Initializes a new instance of the ImageFrameGray8 class.
				/// </summary>
				/// <param name="width">Image width in pixels.</param>
				/// <param name="height">Image height in pixels.</param>
				ImageFrameGray8(int width, int height) : ImageFrame(width, height) 
				{
				}
			};

			/// <summary>
			/// Image class for RGB (3 unsigned chars per pixel) image.
			/// </summary>
			[DataContract]
			public ref class ImageFrameRgb : ImageFrame<RgbPix>
			{
			public:
				/// <summary>
				/// Initializes a new instance of the <see cref="ImageFrameRgb"/> class.
				/// </summary>
				/// <param name="width">Image width in pixels.</param>
				/// <param name="height">Image height in pixels.</param>
				ImageFrameRgb(int width, int height) : ImageFrame(width, height) 
				{
				}

				/// <summary>
				/// Initializes a new instance of the <see cref="ImageFrameRgb"/> class, from another instance
				/// Performs shallow initialization
				/// </summary>
				ImageFrameRgb(ImageFrame<RgbPix>^ other) : ImageFrame<RgbPix>(other) 
				{
				}

				/// <summary>
				/// return data as a byte array
				/// </summary>
				array<unsigned char>^ GetAsByteArray()
				{
					array<unsigned char>^ arr = gcnew array<unsigned char>(this->Stride * this->Height);
					int k = 0;

					for (int i = 0; i < this->ImageData->Length; ++i)
					{
						RgbPix pix = this->ImageData[i];
						arr[k++] = pix.Red;
						arr[k++] = pix.Green;
						arr[k++] = pix.Blue;
					}

					return arr;
				}

				/// <summary>
				/// Implicit conversion to an ImageFrameRGB class
				/// </summary>
				static operator ImageFrameRgb^(ImageFrame<RgbPix>^ image)
				{
					return gcnew ImageFrameRgb(image);
				}
			};

			/// <summary>
			/// Image class for YUY2 image.
			/// </summary>
			[DataContract]
			public ref class ImageFrameYuy2 : ImageFrame<Yuy2Pix>
			{
			public:
				/// <summary>
				/// Initializes a new instance of the <see cref="ImageFrameYuy2"/> class.
				/// </summary>
				/// <param name="width">Image width in pixels.</param>
				/// <param name="height">Image height in pixels.</param>
				ImageFrameYuy2(int width, int height) : ImageFrame(width, height) 
				{
                    if (width * height % 2 != 0)
                    {
                        throw gcnew ArgumentException(String::Format("ImageFrameYuy2 must have an even number of pixels:  {0} x {1} are invalid dimensions", width, height));
                    }
				}

				/// <summary>
				/// Initializes a new instance of the <see cref="ImageFrameYuy2"/> class, from another
				/// instance of the image, making a shallow copy of the original image
				/// </summary>
				/// <param name="other">Original image frame</param>
				ImageFrameYuy2(ImageFrame<Yuy2Pix>^ other) : ImageFrame<Yuy2Pix>(other) 
				{
                    if (this->Width * this->Height % 2 != 0)
                    {
                        throw gcnew ArgumentException(String::Format("ImageFrameYuy2 must have an even number of pixels:  {0} x {1} are invalid dimensions", this->Width, this->Height));
                    }
				}

				/// <summary>
				/// Implicit conversion to an ImageFrameYuy2
				/// </summary>
				static operator ImageFrameYuy2^(ImageFrame<Yuy2Pix>^ image)
				{
					return gcnew ImageFrameYuy2(image);
				}
			};

			/// <summary>
			/// Image class for surface normal
			/// </summary>
			//// [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "Image classes belong together.")]
			[DataContract]
			public ref class ImageFrameNormal : ImageFrame<PixNormal>
			{
			public:
				/// <summary>
				/// Initializes a new instance of the <see cref="ImageFrameNormal"/> class.
				/// </summary>
				/// <param name="width">Image width in pixels.</param>
				/// <param name="height">Image height in pixels.</param>
				ImageFrameNormal(int width, int height) : ImageFrame(width, height) 
				{
				}

				/// <summary>
				/// Initializes a new instance of the <see cref="ImageFrameNormal"/> class.
				/// from a depth image
				/// </summary>
				/// <param name="depthImage">Depth image</param>
				/// <param name="fx">Focal length x</param>
				/// <param name="fy">Focal length y</param>
				ImageFrameNormal(ImageFrameDepth^ depthImage, double fx, double fy);

				/// <summary>
				/// Gets the x focal length
				/// from a depth image
				/// </summary>
				property double FocalX
				{
					double get()
					{
						return this->fx;
					}
				}

				/// <summary>
				/// Gets the y focal length
				/// from a depth image
				/// </summary>
				property double FocalY
				{
					double get()
					{
						return this->fy;
					}
				}

			private:

				/// <summary>
				/// Heavy lifting to initialize frame normal
				/// </summary>
				/// <param name="depthImage">Depth image</param>
				void Init(ImageFrameDepth^ depthImage);

				/// <summary>
				/// Compute normal at a general row column location
				/// </summary>
				/// <param name="depthImage">Depth image</param>
				/// <param name="col">Column location</param>
				/// <param name="row">Row location</param>
				PixNormal ComputeNormals(ImageFrameDepth^ depthImage, int col, int row);

				/// <summary>
				/// Compute normal in the first column
				/// </summary>
				/// <param name="depthImage">Depth image</param>
				/// <param name="row">Row location</param>
				PixNormal ImageFrameNormal::ComputeStartColNormals(ImageFrameDepth^ depthImage, int row);

				/// <summary>
				/// Compute normal in the last column
				/// </summary>
				/// <param name="depthImage">Depth image</param>
				/// <param name="row">Row location</param>
				PixNormal ImageFrameNormal::ComputeEndColNormals(ImageFrameDepth^ depthImage, int row);

				/// <summary>
				/// Compute normal in the first row
				/// </summary>
				/// <param name="depthImage">Depth image</param>
				/// <param name="col">col location</param>
				PixNormal ImageFrameNormal::ComputeStartRowNormals(ImageFrameDepth^ depthImage, int col);

				/// <summary>
				/// Compute normal in the last row
				/// </summary>
				/// <param name="depthImage">Depth image</param>
				/// <param name="col">col location</param>
				PixNormal ImageFrameNormal::ComputeEndRowNormals(ImageFrameDepth^ depthImage, int col);

				/// <summary>
				/// focal length - x
				/// </summary>
				double fx;

				/// <summary>
				/// focal length - y
				/// </summary>
				double fy;
			};

		}
	}
}
